package com.yumeng.webapp.config;

import com.yumeng.common.auth.BaseAuthInfo;
import com.yumeng.common.data.enums.AuthMode;
import com.yumeng.framework.auth.bean.AuthExtDataInfo;
import com.yumeng.framework.auth.bean.authinfo.SimpleAuthInfo;
import com.yumeng.framework.auth.data.constants.OAuth2Constants;
import com.yumeng.framework.auth.security.config.SecurityConfiguration;
import com.yumeng.framework.auth.security.custom.AuthInfoResolver;
import com.yumeng.framework.auth.service.AuthExtDataHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * SpringSecurity配置文件
 *
 * @author wxd
 * @date 2024/6/13 上午10:00
 */
@Slf4j
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig extends SecurityConfiguration {

    /*private DataSource dataSource;
    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Override
    protected PersistentTokenRepository getPersistentTokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        tokenRepository.setCreateTableOnStartup(false);
        return tokenRepository;
    }

    @Override
    protected RememberMeModel getRememberMeModel() {
        return RememberMeModel.PERSISTENT_REP;
    }*/

/*    @Override
    protected boolean getAlwaysRememberMe() {
        return true;
    }*/

    @Override
    protected boolean enableSmsAuth() {
        return false;
    }

    @Override
    protected boolean enableCaptcha(){
        return false;
    }

    @Override
    protected boolean enableOAuth2() {
        return false;
    }

    @Override
    protected boolean useDefaultLoginUrl() {
        return false;
    }

    @Override
    protected Map<String, String> getHasRoleMap() {
        return Map.of("/perm2/**", "aa");
    }

    @Override
    protected String[] getPermitAllUrls() {
        return new String[]{"/demo/testMethod.do","/test/**"};
    }

    /**
     * 注销时需要删除的cookie
     */
    protected String[] getDeleteCookies() {
        return new String[]{"test-cookie", "test-cookie2"};
    }

    @Override
    protected Integer getMaxSessions() {
        return 1;
    }

    @Override
    protected boolean maxSessionsPreventsLogin() {
        return true;
    }

    /**
     * 自定义权限注解表达式
     *
     * @return
     */
/*    @Bean
    protected MethodSecurityExpressionHandler methodSecurityExpressionHandler() {
        return new CustomMethodSecurityExpressionHandler();
    }*/

    /**
     * 自定义认证扩展信息
     *
     * @return
     */
    @Bean
    public AuthExtDataHandler authExtDataHandler(){
        return authInfo -> {
            AuthExtDataInfo dataInfo = new AuthExtDataInfo();
            Map<String, Object> dataMap = Map.of("entId", 12344, "userName", "wxd");
            Map<String, Object> loginInfo = Map.of("authEntId", "33333", "authUserName", "4444444444");
            dataInfo.setDataMap(dataMap);
            dataInfo.setLoginInfo(loginInfo);
            return dataInfo;
        };
    }


    /**
     * 自定义用户信息解析器
     * TODO 如何整合到框架中（单独分出一个模块？因为一般项目不会引入OAuth模块，因此一些具体的类型无法写到framework模块）
     * @return
     */
    @Bean
    public AuthInfoResolver authInfoResolver(){
        return new AuthInfoResolver() {
            @Override
            public BaseAuthInfo resolve(Authentication authentication) {
                BaseAuthInfo authInfo = null;
                if (authentication instanceof OAuth2AuthenticationToken oAuth2Token){
                    SimpleAuthInfo.SimpleAuthInfoType authType = null;
                    if (OAuth2Constants.REGISTRATION_ID_GITEE.equals(oAuth2Token.getAuthorizedClientRegistrationId())){
                        authType = SimpleAuthInfo.SimpleAuthInfoType.OAuth2_Gitee;
                    }
                    if (authType == null){
                        return null;
                    }
                    if (oAuth2Token.getPrincipal() instanceof DefaultOAuth2User defaultOAuth2User){
                        Set<String> authorities = defaultOAuth2User.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
                        authInfo = new SimpleAuthInfo(authType, AuthMode.OAUTH2
                                , String.valueOf((Object) defaultOAuth2User.getAttribute("id"))
                                , String.valueOf((Object)defaultOAuth2User.getAttribute("name"))
                                , String.valueOf((Object)defaultOAuth2User.getAttribute("login"))
                                , authorities);
                    }
                }
                return authInfo;
            }
        };
    }

//==========================================================================================================================================
    //private final boolean enabledCsrf = true;
    //private final boolean enabledRememberMe = true;
    //, RememberMeServices rememberMeServices
    //@Bean
    /*public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
                //配置所有的Http请求必须认证
                .authorizeHttpRequests(authorize -> {
                    //说明手动指定了static资源路径，可以批量放行；若没有指定，则不批量放行
                    if (StringUtils.isNotBlank(MvcUtils.getStaticPath(webMvcProperties))) {
                        authorize.requestMatchers(webMvcProperties.getStaticPathPattern()).permitAll();
                    }
                    authorize
                            //.requestMatchers("/res/staticLogin.html").permitAll()
                            .requestMatchers(yumengProperties.getInfo().getLoginUrl()).permitAll()
                            .requestMatchers(serverProperties.getError().getPath()).permitAll()
                            .requestMatchers("/res/danger.html").permitAll()

                            // 以/user/开头的请求，必须拥有 ADMIN角色
                            .requestMatchers("/perm/**").hasRole("admin")//.hasAuthority("PERM_list")
                            // 以/db/开头的请求，必须同时拥有ADMIN、DBA角色
                            //.requestMatchers("/perm2/**").access(new WebExpressionAuthorizationManager("hasRole('admin') and hasRole('dba')"))
                            //.requestMatchers("/db/**").access(AuthorizationManagers.allOf(AuthorityAuthorizationManager.hasRole("ADMIN"), AuthorityAuthorizationManager.hasRole("DBA")))

                            // /test/开头的所有POST请求的用户，必须具有ADMIN或者USER角色
                            //.requestMatchers(new AntPathRequestMatcher("/test/**","POST")).hasAnyRole("ADMIN","USER")


                            // 其他请求没有被以上规则匹配时，将会拒绝访问
                            //.anyRequest().denyAll()

                            .anyRequest().authenticated();
                })
                //开启表单登录
                .formLogin(formLogin -> formLogin
                                //.loginPage("/res/staticLogin.html")//自定义登录页面（注意同步配置loginProcessingUrl）
                                .loginPage(yumengProperties.getInfo().getLoginUrl())//自定义登录页面（注意同步配置loginProcessingUrl）
                                .loginProcessingUrl("/login")//自定义登录处理URL
                                .usernameParameter("name")//自定义用户名参数名称
                                .passwordParameter("pwd")//自定义密码参数名称
                                .defaultSuccessUrl("/page/welcome.vw")//自定义登录成功RUL  /res/success.html /security/testServletApi.do
                                .failureUrl("/res/failure.html")//自定义登录失败RUL
                        //.successHandler(new JsonAuthenticationSuccessHandler())//认证成功处理器（JSON）（用于前后端分离）
                        //.failureHandler(new JsonAuthenticationFailureHandler())//认证失败处理器（JSON）（用于前后端分离）
                )
                //禁用注销功能
                //.logout(AbstractHttpConfigurer :: disable)
                .logout(logout -> logout
                                .logoutUrl("/custom/logout")
                                .deleteCookies("JSESSIONID")
                                .addLogoutHandler(new MyLogoutHandler()) //自定义注销处理器
                        //.logoutSuccessHandler(new JsonLogoutSuccessHandler())//自定义注销成功处理器
                        //.deleteCookies("xxx", "yyy")// 删除某些指定 cookie, 添加一个CookieClearingLogoutHandler
                        //.clearAuthentication(true) // 清理Authentication ，默认true
                        //.invalidateHttpSession(true) // 设置当前登录用户Session（保存登录后的用户信息）无效，默认true
                        //.logoutSuccessUrl("/")//自定义注销成功跳转地址

                        //与logoutUrl 2选1
*//*                        .logoutRequestMatcher(new OrRequestMatcher(
                                new AntPathRequestMatcher("/aaLogOut"),
                                new AntPathRequestMatcher("/bbLogOut")
                        ))*//*
                )
                //会话创建策略 默认IF_REQUIRED
                .sessionManagement(session -> session
                        .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)//创建策略

                        //.invalidSessionUrl("/res/login.html?error=INVALID_SESSION")//失效跳转路径
                        .invalidSessionStrategy(new MyInvalidSessionStrategy())//session超时 前后端分离

                        //.sessionFixation(SessionManagementConfigurer.SessionFixationConfigurer :: changeSessionId)//会话固定攻击保护策略 默认 changeSessionId
                        .maximumSessions(1)  // 用户最大会话数为 1，后面的登陆就会自动踢掉前面的登陆
                        //.maxSessionsPreventsLogin(true) // 当前已登录时，阻止其他登录

                        .expiredSessionStrategy(new MySessionInformationExpiredStrategy())//在线踢人 前后端分离
                )
                //自定义时需关闭，否则会有异常（要支持csrf可能需要额外配置）AbstractHttpConfigurer::disable Customizer.withDefaults()
                .csrf((csrf) -> {
                            if (enabledCsrf) {
                                //Customizer.withDefaults();
                                csrf
                                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                                        .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler());
                            } else {
                                csrf.disable();
                            }
                        }
                )
                .rememberMe(rm -> {
                            if (enabledRememberMe) {
                                rm
                                        .alwaysRemember(true)//始终开启记住我
                                        .useSecureCookie(true)//是否只支持https
                                        .rememberMeCookieDomain("127.0.0.1")//可以访问该cookie的域名
                                        .rememberMeParameter("m-rbe-param")//传递的参数
                                        .rememberMeCookieName("m-rbe-c-key")//配置自定义cookie名
                                        .tokenValiditySeconds(60 * 60 * 24 * 7)//记住我有效时间
                                        .key("my-rmb-me-key-9527-869883641814097920");

                                        //.rememberMeServices(rememberMeServices);
                            } else {
                                rm.disable();
                            }

                        }
                );

        //=====================================================================================================
        //.httpBasic(Customizer.withDefaults())
        //.passwordManagement(Customizer.withDefaults())
        //.rememberMe(Customizer.withDefaults())
        //.addFilterAfter(new TestFilter(), AuthorizationFilter.class);
        return http.build();
    }*/




    /**
     * 注入自定义UserDetailsServiceBean
     * 默认的InMemoryUserDetailsManager
     *
     */
/*    @Bean
    public MyUserDetailsServiceImpl myUserDetailsServiceImpl(SysUserServiceImpl sysUserService) {
        log.info("注入【UserDetailsService】:MyUserDetailsServiceImpl");
        return new MyUserDetailsServiceImpl(sysUserService);
    }*/


    /**
     * 监听会话生命周期事件
     * <p>
     * 当session变化时，通知spring知道。
     * 否则会出现：当限制会话并发数时，在a处已注销，但是b处还是无法登录
     *
     */
/*    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }*/



    //================================================================================================================================================



    /**
     * 需要注入spring中，否则无法自动触发initDao()
     * 用于rememberMeServices
     * @param dataSource
     * @return
     */
    //@Bean
    public JdbcTokenRepositoryImpl tokenRepository(DataSource dataSource) {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        // create table persistent_logins (username varchar(64) not null, series varchar(64) primary key, token varchar(64) not null, last_used timestamp not null)
        tokenRepository.setCreateTableOnStartup(false);
        return tokenRepository;
    }

    /**
     * 密码编码器
     * 不指定时，默认为 PasswordEncoderFactories.createDelegatingPasswordEncoder()
     *
     * @return
     */
    //@Bean
    public PasswordEncoder passwordEncoder() {
/*        log.info("注入【密码编码器】：BCryptPasswordEncoder");
        return new BCryptPasswordEncoder();*/


        //测试密码策略升级
        String encodingId = "MD5";
        Map<String, PasswordEncoder> encoders = new HashMap<>();
        encoders.put("bcrypt", new BCryptPasswordEncoder());
        encoders.put("MD4", new org.springframework.security.crypto.password.Md4PasswordEncoder());
        encoders.put("MD5", new org.springframework.security.crypto.password.MessageDigestPasswordEncoder("MD5"));
        encoders.put("noop", org.springframework.security.crypto.password.NoOpPasswordEncoder.getInstance());
        return new DelegatingPasswordEncoder(encodingId, encoders);
    }




}
